// Author: wsfuyibing <682805@qq.com>
// Date: 2025-05-09

package uploader

import (
    "context"
    "errors"
    "fmt"
    "gitee.com/go-libs/log"
    "io"
    "math/rand"
    "mime/multipart"
    "os"
    "regexp"
    "server/app/config"
    "strings"
    "time"
)

var (
    regexMatchFileExtensionName = regexp.MustCompile(`\.([a-zA-Z0-9]+)$`)
)

type (
    Manager interface {
        Upload(ctx context.Context, header *multipart.FileHeader) (url string, err error)
        Uploads(ctx context.Context, headers []*multipart.FileHeader) (list []string, err error)
    }

    manager struct {
    }
)

func New() Manager {
    return &manager{}
}

// Upload
// 上传单个文件.
func (o *manager) Upload(ctx context.Context, header *multipart.FileHeader) (url string, err error) {
    var (
        file             multipart.File
        out              *os.File
        path, name, dest string
    )

    // 1. 监听结束.
    defer func() {
        if r := recover(); r != nil {
            log.Fatalfc(ctx, `[uploader] upload caused an panic: fatal="%v"`, r)

            if err == nil {
                err = fmt.Errorf(`%v`, r)
            }
        } else if err != nil {
            log.Errorfc(ctx, `[uploader] upload failed: error="%v"`, err)
        } else {
            log.Infofc(ctx, `[uploader] upload success: target="%s"`, dest)
        }
    }()

    // 2. 准备上传.
    if path, name, err = o.generate(header); err != nil {
        return
    } else {
        dest = fmt.Sprintf(`%s/%s/%s`, config.Upload.GetBasePath(), path, name)
    }

    // 3. 打开文件.
    if file, err = header.Open(); err != nil {
        return
    }

    // 3.1 关闭文件.
    defer func() {
        _ = file.Close()
    }()

    // 4. 创建目标.
    if out, err = os.Create(dest); err != nil {
        return
    }

    // 4.1 关闭目标.
    defer func() {
        _ = out.Close()
    }()

    // 5. 复制内容.
    if _, err = io.Copy(out, file); err == nil {
        url = fmt.Sprintf(`%s/%s/%s`, config.Upload.GetHost(), path, name)
    }
    return
}

// Uploads
// 多文件并行上传.
func (o *manager) Uploads(ctx context.Context, headers []*multipart.FileHeader) (list []string, err error) {
    var url string

    list = make([]string, 0)

    for _, header := range headers {
        if header == nil {
            continue
        }
        if url, err = o.Upload(ctx, header); err != nil {
            break
        } else {
            list = append(list, url)
        }
    }
    return
}

// 生成数据.
//
//   return "2025-05/2025-05-09", "example.jpg", nil
func (o *manager) generate(header *multipart.FileHeader) (path, name string, err error) {
    var (
        ext, local string
        info       os.FileInfo
        tm         = time.Now()
    )

    // 1. 文件类型.
    //    从原始文件名(非MIME)提供扩展名.
    if m := regexMatchFileExtensionName.FindStringSubmatch(header.Filename); len(m) == 2 {
        ext = strings.ToLower(m[1])
    } else {
        err = fmt.Errorf(`unknown file extension name: %s`, header.Filename)
        return
    }

    // 2. 计算路径.
    //    例如: /data/app/upload/2025-05/2025-05-09
    path = fmt.Sprintf(`%s/%s`, config.Upload.GetSeparator(tm), config.Upload.GetFolder(tm))
    local = fmt.Sprintf(`%s/%s`, config.Upload.GetBasePath(), path)

    // 3. 检查路径.
    //    若目标路径不存在则创建
    if info, err = os.Stat(local); err != nil {
        // 3.1 创建目录.
        if errors.Is(err, os.ErrNotExist) {
            err = os.MkdirAll(local, os.ModePerm)
        }
    } else if !info.IsDir() {
        // 3.2 目录错误.
        //     指定路径不是合法的目录.
        err = fmt.Errorf(`not a valid directory: %s`, path)
    }

    // 3. 生成名称.
    if err == nil {
        name = fmt.Sprintf(`%v%010d.%s`, tm.UnixMicro(), rand.Int63n(9999999999), ext)
    }
    return
}
